#pragma once 

#include "cvm_cpp.h"

#include <set>
#include <map>
#include <vector>

namespace cvm
{
	struct Assembler
	{
		std::set<Token*> tkns;
		std::map<Define*, size_t> defs;
		std::vector<byte> ops;
		std::vector<::Val> odata;
		std::vector<int> adata;
		std::vector<int> kdata;
		std::vector<Define*> definesToProcess;
		std::vector<Function*> functions;

		Assembler(Define* define)
		{			
			definesToProcess.push_back(define);
			while (definesToProcess.size() > 0) {
				Define* next = definesToProcess.back();
				definesToProcess.pop_back();
				ProcessDefine(next);
			}
		}

		~Assembler()
		{			
		}

		void ProcessDefine(Define* define)
		{
			if (defs.find(define) != defs.end()) 
				return;
			
			for (size_t i=0; i < define->Count(); ++i)
				ProcessToken(define->At(i));
		}

		void ProcessToken(Token* tkn)
		{
			if (tkns.find(tkn) != tkns.end()) return;
			tkns.insert(tkn);

			if (tkn->Is<Push<int> >())
			{
				Push<int>* pInt = tkn->As<Push<int> >();
				long n = pInt->value;
				ops.push_back(_long);
				byte* b = (byte*)&n;
				for (int i=0; i < sizeof(n); ++i) 
					ops.push_back(*b++);
			}
			else if (tkn->Is<Op>())
			{
				Op* pOp = tkn->As<Op>();
				ops.push_back(pOp->opcode);
			}
			else if (tkn->Is<Function>())
			{
				Function* pFun = tkn->As<Function>();
				int n = (int)functions.size();
				functions.push_back(pFun);
				
				ops.push_back(_flong);
				byte* b = (byte*)&n;
				for (int i=0; i < sizeof(n); ++i) 
					ops.push_back(*b++);

				definesToProcess.push_back(pFun->define);
			}
			else if (tkn->Is<OpArg>())
			{
				OpArg* pByte = tkn->As<OpArg>();
				ops.push_back(pByte->n);
			}
			else
			{
				throw std::exception("Unhandled token");
			}
		} 

		/// The VM is assumed to be initialized already via a call to vm_init().
		void ApplyToVM(::VM* vm)
		{
			// Array data not handled yet.
			assert(adata.size() == 0);
			
			// Initialize the object look-up table.
			::Val table;
			table_init(&table);
			
			// Initialize the VM.
			vm_init(vm);

			// Load code 
			byte* tmp = new byte[ops.size()];
			for (size_t i=0; i < ops.size(); ++i)
				tmp[i] = ops[i];
			vm_load_code(vm, tmp, ops.size());
			delete[] tmp;

			// Allocate objects 
			for (size_t i=0; i < odata.size(); ++i) {
				::Val* v = val_new(odata[i].type);
				*v = odata[i];

				// Add the newly allocated object to a pointer lookup table
				table_add(&table, i, v);
				
				// There should be no code pointer 
				assert(!obj_code(v)); 

				// Objects with arrays are not currently supported
				assert(!obj_array(v));
			}

			// Load constants			
			int n = (int)kdata.size();
			array_alloc(&vm->constants, n);
			vm->constants.cap = n;
			vm->constants.cnt = n;
			for (int i=0; i < n; ++i) {
				// Look-up the pointer in the pointer table 
				::Val* v = table_find(&table, kdata[i])->val;
				assert(v);
				vm_constant(i) = v;
			}
		}
	};
}

